//
// Created by hunan on 25-4-4.
//

#include "flv_parser.h"

#include "./../amf_module/amf.h"
#include "./../string_util/string_util.h"
#include "flv_message.h"

#include <cstdint>
#include <cstdlib>
#include <cstring>

int FlvParser::ParseFlvHeader(const uint8_t * data, int data_length, int & use_length)
{
    if (data_length < (9 + 4))
    {
        return NEED_DATA;
    }
    uint8_t F     = 'F';
    uint8_t L     = 'L';
    uint8_t V     = 'V';
    int     index = 0;
    if (*(data) == F && *(data + 1) == L && *(data + 2) == V)
    {
        index                              += 3;
        struct FlvMessage::FlvHeader header = {};
        header.m_signature                  = "FLV";
        header.m_version                    = *(data + index);
        if (header.m_version <= 0)
        {
            return BAD;
        }
        index               += 1;
        header.m_audio_flag  = *(data + index) & 0b00000100;
        header.m_video_flag  = *(data + index) & 0b00000001;
        index               += 1;
        uint32_t data_offset = 0;
        memcpy(&data_offset, data + index, sizeof(data_offset));
        data_offset          = StringUtil::BeToH32(data_offset);
        index               += 4;
        index               += 4;
        header.m_data_offset = static_cast<int>(data_offset);
        // add to flv message
        m_flv_message.FlvHeader(header);
        use_length = index;
        return FINISH;
    }
    else
    {
        return BAD;
    }
}

int FlvParser::ParseFlvTagHeader(const uint8_t * data, int data_length, int & use_length, FlvMessage::FlvTag & tag)
{
    if (data_length < 11)
    {
        return BAD;
    }
    int     index          = 0;
    uint8_t media_type     = *(data + index);
    tag.m_tag_type         = media_type;
    index                 += 1;
    uint32_t tag_data_size = 0;
    uint32_t time_stamp    = 0;
    auto *   item          = reinterpret_cast<uint8_t *>(&tag_data_size);
    item++;
    memcpy(item, data + index, 3);
    tag_data_size   = StringUtil::BeToH32(tag_data_size);
    tag.m_data_size = static_cast<int>(tag_data_size);
    index          += 3;
    item            = reinterpret_cast<uint8_t *>(&time_stamp);
    item++;
    memcpy(item, data + index, 3);
    time_stamp       = StringUtil::BeToH32(time_stamp);
    tag.m_time_stamp = time_stamp;
    index           += 4;
    index           += 3;
    use_length       = index;
    return FINISH;
}

int FlvParser::ParseFlvTagData(const uint8_t * data, int data_length, int & use_length, FlvMessage::FlvTag & tag)
{
    if (tag.m_data_size > data_length)
    {
        return BAD;
    }
    tag.m_data.resize(tag.m_data_size);
    memcpy(tag.m_data.data(), data, tag.m_data_size);
    use_length = tag.m_data_size;
    return FINISH;
}

int FlvParser::ParseFlvTag(const uint8_t * data, int data_length, int & use_length)
{
    if (data_length <= 11)
    {
        return NEED_DATA;
    }
    FlvMessage::FlvTag tag    = {};
    int                status = ParseFlvTagHeader(data, data_length, use_length, tag);
    if (status == FINISH)
    {
        if ((data_length - use_length) >= (tag.m_data_size + 4))
        {
            int use = 0;
            status  = ParseFlvTagData(data + use_length, data_length - use_length, use, tag);
            if (status == FINISH)
            {
                uint32_t tag_length = 0;
                memcpy(&tag_length, (data + use_length + use), sizeof(tag_length));
                tag_length = StringUtil::BeToH32(tag_length);
                if (tag_length == static_cast<uint32_t>(tag.m_data_size + use_length))
                {
                    status      = FINISH;
                    use_length += use + 4;
                }
                else
                {
                    status     = BAD;
                    use_length = data_length;
                }
            }
            else
            {
                use_length = data_length;
                status     = BAD;
            }
        }
        else
        {
            use_length = 0;
            status = NEED_DATA;
        }
    }
    else
    {
        use_length = data_length;
        status     = BAD;
    }
    if (status == FINISH)
    {
        if (tag.m_tag_type == 0x12)
        {
            int        use   = 0;
            AmfValue * value = AmfValue::ParseBuffer(tag.m_data.data(), tag.m_data.size(), use);
            if (value && value->GetValueType() == AMF_STRING)
            {
                // onMetaData
                // printf("%s.\n", value->AmfValueString().c_str());
                delete value;
                value = nullptr;
                value = AmfValue::ParseBuffer(tag.m_data.data() + use, tag.m_data.size() - use, use);
                if (value && value->GetValueType() == AMF_ARRAY_TYPE)
                {
                    auto * object = static_cast<AmfArray *>(value);
                    // update meta
                    m_flv_message.MetaData(object);
                }
            }
        }
        else
        {
            // printf("new tag(%d) data size(%d),\n", tag.m_tag_type, tag.m_data_size);
            m_flv_message.PushTag(tag);
        }
    }
    return status;
}

FlvParser::FlvParser()
{
    m_parser_status = PARSER_INIT;
}

int FlvParser::ParseFlvMessage(const uint8_t * data, int data_length, int & use_length)
{
    int index = 0;
    while (true)
    {
        int status           = INI;
        int parse_use_length = 0;
        switch (m_parser_status)
        {
            case PARSER_INIT:
            {
                status = ParseFlvHeader(data + index, data_length - index, parse_use_length);
                index += parse_use_length;
                if (status == FINISH)
                {
                    m_parser_status = PARSER_FLV_TAG;
                }
                break;
            }
            case PARSER_FLV_TAG:
            {
                status = ParseFlvTag(data + index, data_length - index, parse_use_length);
                index += parse_use_length;
                break;
            }
            default:
                break;
        }
        if (status == BAD)
        {
            use_length      = data_length;
            m_parser_status = PARSER_BAD;
            break;
        }
        if (status == FINISH && m_parser_status == PARSER_FLV_TAG_FINISH)
        {
            use_length = index;
            break;
        }
        if (status == NEED_DATA)
        {
            use_length = index;
            break;
        }
    }
    return m_parser_status;
}

int FlvParser::FlvProtocolStatus() const
{
    return m_parser_status;
}

struct FlvMessage::FlvHeader FlvParser::GetFlvHeader()
{
    return m_flv_message.GetFlvHeader();
}

struct FlvMessage::FlvTag * FlvParser::GetMetaData()
{
    return nullptr;
}

struct FlvMessage::FlvTag * FlvParser::GetFlvTag()
{
    int index = m_flv_message.FlvTagCount();
    return m_flv_message.GetFlvTagByIndex(index);
}
